package com.nulldev.util.scripting.cup.vee;

import java.util.Collections;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import com.nulldev.util.JVM.JVM;
import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.VariableAPI.StringsUtil;
import com.nulldev.util.data.Arrays.maps.FastMaps;
import com.nulldev.util.manage.nullUtil;
import com.nulldev.util.scripting.cup.vee.CUPRegister.CUPRegisterType;
import com.nulldev.util.scripting.cup.vee.registers.CustomCUPRegister;
import com.nulldev.util.scripting.cup.vee.registers.DoubleCUPRegister;
import com.nulldev.util.scripting.cup.vee.registers.ExtendedCUPRegister;
import com.nulldev.util.scripting.cup.vee.registers.FloatCUPRegister;
import com.nulldev.util.scripting.cup.vee.registers.FloatSFPCUPRegister;
import com.nulldev.util.scripting.cup.vee.registers.NormalCUPRegister;
import com.nulldev.util.scripting.cup.vee.registers.ShortCUPRegister;
import com.nulldev.util.scripting.cup.vee.registers.StringCUPRegister;
import com.nulldev.util.scripting.cup.vee.registers.WideCUPRegister;

public class VEERegisterManager {

	private static final Object[] PROTECTED_REGISTER_KEYS =
		{ "SILVER", "VEEVEN", "_SYSSTR" };

	private final Map<Object, CUPRegister<?>> registers;
	private final CUPRegisterType defaultType;
	private final AtomicInteger registerNumberCounter = new AtomicInteger();

	public VEERegisterManager(final CUPRegisterType type) {
		this.registers = FastMaps.uniMap(16, new Object[][]
			{
					/* SIL VERSION (0x1 = 1.0.0) */
					{ "SILVER", new ShortCUPRegister(0).setAndReturn((short) 0x1).lock() },
					/*
					 * VEE VENDOR 0x0 = Generic [...] 0xCAA = MEE: Micro Execution Environment 0xCAB
					 * = nullUtil's VEE [...]
					 */
					{ "VEEVEN", new ShortCUPRegister(0).setAndReturn((short) 0xCAB).lock() },
					{ "_SYSSTR", new StringCUPRegister(0).setAndReturn("CUP on " + nullUtil.getVersionString()).lock() } });
		this.defaultType = type;
	}

	public CUPRegisterType getDefaultType() {
		return this.defaultType;
	}

	public CUPRegister<?> register(final Object reg) {
		return this.registers.get(reg);
	}

	public CUPRegister<?> make(final Object object) {
		if (object instanceof Integer) {
			return this.make((int) object);
		} else if (object instanceof String) {
			return this.make((String) object);
		} else {
			throw new UnsupportedOperationException("Cannot make register from: " + object);
		}
	}

	public CUPRegister<?> make(final int registerNumber) {
		if (registerNumber < 0)
			throw new IllegalArgumentException("Invalid register number: CANNOT BE A NEGATIVE NUMBER");
		final CUPRegister<?> reg;
		switch (this.defaultType) {
			case EXTENDED:
				reg = new ExtendedCUPRegister(registerNumber);
				break;
			case NORMAL:
				reg = new NormalCUPRegister(registerNumber);
				break;
			case SHORT:
				reg = new ShortCUPRegister(registerNumber);
				break;
			case WIDE:
				reg = new WideCUPRegister(registerNumber);
				break;
			case DOUBLE:
				reg = new DoubleCUPRegister(registerNumber);
				break;
			case FLOAT:
				reg = new FloatCUPRegister(registerNumber);
				break;
			case FLOAT_SFP:
				if (JVM.version() <= 16f) {
					reg = new FloatSFPCUPRegister(registerNumber);
					break;
				} else {
					reg = new FloatCUPRegister(registerNumber);
					break;
				}
			case STRING:
				reg = new StringCUPRegister(registerNumber);
				break;
			default:
			case CUSTOM:
				throw new UnsupportedOperationException("Cannot run LNR on CUSTOM registers!");
		}
		this.registerNumberCounter.getAndIncrement();
		this.registers.put(registerNumber, reg);
		return reg;
	}

	public CUPRegister<?> make(final String alias) {
		if (alias == null || alias.isEmpty())
			throw new RuntimeException("Invalid register alias: CANNOT BE NULL OR EMPTY!");
		if (!StringsUtil.isAllASCII(alias))
			throw new RuntimeException("Invalid register alias: CANNOT BE NON-ASCII!");
		final CUPRegister<?> reg;
		switch (this.defaultType) {
			case EXTENDED:
				reg = new ExtendedCUPRegister(this.registerNumberCounter.incrementAndGet());
				break;
			case NORMAL:
				reg = new NormalCUPRegister(this.registerNumberCounter.incrementAndGet());
				break;
			case SHORT:
				reg = new ShortCUPRegister(this.registerNumberCounter.incrementAndGet());
				break;
			case WIDE:
				reg = new WideCUPRegister(this.registerNumberCounter.incrementAndGet());
				break;
			case DOUBLE:
				reg = new DoubleCUPRegister(this.registerNumberCounter.incrementAndGet());
				break;
			case FLOAT:
				reg = new FloatCUPRegister(this.registerNumberCounter.incrementAndGet());
				break;
			case FLOAT_SFP:
				if (JVM.version() <= 16f) {
					reg = new FloatSFPCUPRegister(this.registerNumberCounter.incrementAndGet());
					break;
				} else {
					reg = new FloatCUPRegister(this.registerNumberCounter.incrementAndGet());
					break;
				}
			case STRING:
				reg = new StringCUPRegister(this.registerNumberCounter.incrementAndGet());
				break;
			default:
			case CUSTOM:
				throw new UnsupportedOperationException("Cannot run LNR on CUSTOM registers!");
		}
		this.registers.put(alias, reg);
		return reg;
	}

	public <E> CUPRegister<E> makeCustom(final Object object, final E item) {
		if (object instanceof Integer) {
			return this.makeCustom((int) object, item);
		} else if (object instanceof String) {
			return this.makeCustom((String) object, item);
		} else {
			throw new UnsupportedOperationException("Cannot make register from: " + object);
		}
	}

	public <E> CUPRegister<E> makeCustom(final int registerNumber, final E e) {
		if (registerNumber < 0)
			throw new IllegalArgumentException("Invalid register number: CANNOT BE A NEGATIVE NUMBER");
		final CUPRegister<E> reg = new CustomCUPRegister<E>(registerNumber).setAndReturn(e);
		this.registerNumberCounter.getAndIncrement();
		this.registers.put(registerNumber, reg);
		return reg;
	}

	public <E> CUPRegister<E> makeCustom(final String alias, final E e) {
		if (alias == null || alias.isEmpty())
			throw new RuntimeException("Invalid register alias: CANNOT BE NULL OR EMPTY!");
		if (!StringsUtil.isAllASCII(alias))
			throw new RuntimeException("Invalid register alias: CANNOT BE NON-ASCII!");
		final CUPRegister<E> reg = new CustomCUPRegister<E>(this.registerNumberCounter.incrementAndGet()).setAndReturn(e);
		this.registers.put(alias, reg);
		return reg;
	}

	public Map<Object, CUPRegister<?>> asMap() {
		return Collections.unmodifiableMap(this.registers);
	}

	/* make with (O)bject and (T)ype */
	public CUPRegister<?> makeOT(final Object alias, final short type) {
		if (alias == null)
			throw new IllegalArgumentException("Invalid register alias: ALIAS CANNOT BE NULL");
		if (alias instanceof Number && ((Number) alias).intValue() < 0)
			throw new IllegalArgumentException("Invalid register alias: ALIAS CANNOT BE NEGATIVE");
		final CUPRegister<?> reg;
		switch (type) {
			case 0x3:
				reg = new ExtendedCUPRegister(this.registerNumberCounter.incrementAndGet());
				break;
			case 0x2:
				reg = new NormalCUPRegister(this.registerNumberCounter.incrementAndGet());
				break;
			case 0x1:
				reg = new ShortCUPRegister(this.registerNumberCounter.incrementAndGet());
				break;
			case 0x4:
				reg = new WideCUPRegister(this.registerNumberCounter.incrementAndGet());
				break;
			case 0x6:
				reg = new StringCUPRegister(this.registerNumberCounter.incrementAndGet());
				break;
			case 0x7:
				reg = new FloatCUPRegister(this.registerNumberCounter.incrementAndGet());
				break;
			case 0x8:
				if (JVM.version() <= 16f) {
					reg = new FloatSFPCUPRegister(this.registerNumberCounter.incrementAndGet());
					break;
				} else {
					reg = new FloatCUPRegister(this.registerNumberCounter.incrementAndGet());
					break;
				}
			case 0x9:
				reg = new DoubleCUPRegister(this.registerNumberCounter.incrementAndGet());
				break;
			case 0x5:
				throw new UnsupportedOperationException("Cannot run LNR on CUSTOM registers!");
			default:
				throw new IllegalArgumentException("Unknown register type: " + type);
		}
		this.registers.put(alias, reg);
		return reg;
	}

	/* make with (I)nt and (T)ype */
	public CUPRegister<?> makeIT(final int registerNumber, final short type) {
		if (registerNumber < 0)
			throw new IllegalArgumentException("Invalid register number: CANNOT BE A NEGATIVE NUMBER");
		final CUPRegister<?> reg;
		switch (type) {
			case 0x3:
				reg = new ExtendedCUPRegister(this.registerNumberCounter.incrementAndGet());
				break;
			case 0x2:
				reg = new NormalCUPRegister(this.registerNumberCounter.incrementAndGet());
				break;
			case 0x1:
				reg = new ShortCUPRegister(this.registerNumberCounter.incrementAndGet());
				break;
			case 0x4:
				reg = new WideCUPRegister(this.registerNumberCounter.incrementAndGet());
				break;
			case 0x6:
				reg = new StringCUPRegister(this.registerNumberCounter.incrementAndGet());
				break;
			case 0x7:
				reg = new FloatCUPRegister(this.registerNumberCounter.incrementAndGet());
				break;
			case 0x8:
				if (JVM.version() <= 16f) {
					reg = new FloatSFPCUPRegister(this.registerNumberCounter.incrementAndGet());
					break;
				} else {
					reg = new FloatCUPRegister(this.registerNumberCounter.incrementAndGet());
					break;
				}
			case 0x9:
				reg = new DoubleCUPRegister(this.registerNumberCounter.incrementAndGet());
				break;
			case 0x5:
				throw new UnsupportedOperationException("Cannot run LNR on CUSTOM registers!");
			default:
				throw new IllegalArgumentException("Unknown register type: " + type);
		}
		this.registers.put(registerNumber, reg);
		return reg;
	}

	public CUPRegister<?> getRegister(final Object obj) {
		return this.registers.get(obj);
	}

	public CUPRegister<?> removeRegister(final Object reg) {
		return this.registers.remove(reg);
	}

	public boolean isProtected(final Object _regA) {
		return ArrayUtils.contains(PROTECTED_REGISTER_KEYS, _regA);
	}

	public boolean isRegister(final Object objB) {
		if (!(objB instanceof String))
			return false;
		return this.registers.containsKey(objB);
	}
}
